#ifndef __ovip_third_nutshell_simple_bus__
#define __ovip_third_nutshell_simple_bus__
#include "xspcomm/xport.h"
#include "xspcomm/xcallback.h"
#include "openvip/ovip.h"
#include <cstdint>

namespace ovip {
class SimpleBusWrapper
{
public:
    // request_cmd
    u_int8_t cmd_read       = 0b0000;
    u_int8_t cmd_write      = 0b0001;
    u_int8_t cmd_readBurst  = 0b0010;
    u_int8_t cmd_writeBurst = 0b0011;
    u_int8_t cmd_writeLast  = 0b0111;
    u_int8_t cmd_probe      = 0b1000;
    u_int8_t cmd_prefetch   = 0b0100;
    // response_cmd
    uint8_t cmd_readLast  = 0b0110;
    uint8_t cmd_writeResp = 0b0101;
    uint8_t cmd_probeHit  = 0b1100;
    uint8_t cmd_probMiss  = 0b1000;
    // ports
    xspcomm::XPort port;
    SimpleBusWrapper(xspcomm::XPort &port, std::string prefix = "")
    {
        this->port = port.NewSubPort(prefix);
    }
    bool IsReqReady() { return this->port["req_ready"] == 1; }
    void ReqSetValid() { this->port["req_valid"] = 1; }
    void ReqUnValid() { this->port["req_valid"] = 0; }
    u_int64_t ReqReadData(u_int32_t addr)
    {
        this->port["resp_ready"]    = 1;
        this->port["req_bits_user"] = 0x123;
        this->port["req_bits_addr"] = addr;
        this->port["req_bits_size"] = 7;
        this->port["req_bits_cmd"]  = this->cmd_read;
        return (u_int64_t)this->port["resp_bits_rdata"];
    }
    bool ReqWriteData(u_int32_t addr, u_int64_t data, unsigned char mask)
    {
        this->port["resp_ready"]     = 1;
        this->port["req_bits_addr"]  = addr;
        this->port["req_bits_size"]  = 7;
        this->port["req_bits_cmd"]   = this->cmd_write;
        this->port["req_bits_wmask"] = mask;
        this->port["req_bits_wdata"] = data;
        return true;
    }
    bool RespWriteData(u_int32_t cmd, u_int64_t data)
    {
        this->port["resp_valid"]      = 1;
        this->port["resp_bits_cmd"]   = cmd;
        this->port["resp_bits_rdata"] = data;
        return true;
    }
    bool RespReadData(u_int32_t cmd, u_int64_t data)
    {
        return this->RespWriteData(cmd, data);
    }
    u_int64_t RespData() { return this->port["resp_bits_rdata"]; }
    bool IsRespReady() { return this->port["resp_ready"] == 1; }
    bool IsReqValid() { return this->port["req_valid"] == 1; }
    bool IsRespValid() { return this->port["resp_valid"] == 1; }
    bool IsRespWriteCmp()
    {
        return this->port["resp_bits_cmd"] == this->cmd_writeResp;
    }

    bool IsReqRead()
    {
        return this->port["req_bits_cmd"] == this->cmd_read
               && this->IsReqValid();
    }
    bool IsReqReadBurst()
    {
        return this->port["req_bits_cmd"] == this->cmd_readBurst
               && this->IsReqValid();
    }
    bool IsReqWrite()
    {
        return this->port["req_bits_cmd"] == this->cmd_write
               && this->IsReqValid();
    }
    bool IsReqWriteBurst()
    {
        return this->port["req_bits_cmd"] == this->cmd_writeBurst
               && this->IsReqValid();
    }
    bool IsReqWriteLast()
    {
        return this->port["req_bits_cmd"] == this->cmd_writeLast
               && this->IsReqValid();
    }

    void Zero() { this->port.SetZero(); }

    std::string str_cmd()
    {
        if (this->IsReqReady()) {
            switch (this->port["req_bits_cmd"].U()) {
            case 0b0000: // read
                return "read";
            case 0b0001: // write
                return "write";
            case 0b0010: // readBurst
                return "readBurst";
            case 0b0011: // writeBurst
                return "writeBurst";
            case 0b0111: // writeLast
                return "writeLast";
            case 0b1000: // probe
                return "probe";
            case 0b0100: // prefetch
                return "prefetch";
            default: return "unknown";
            };
        }
        if (this->IsRespReady()) {
            switch (this->port["resp_bits_cmd"].U()) {
            case 0b0110: // readLast
                return "readLast";
            case 0b0101: // writeResp
                return "writeResp";
            case 0b1100: // probeHit
                return "probeHit";
            case 0b1000: // probMiss
                return "probMiss";
            default: return "unknown";
            };
        }
    }
    std::string String()
    {
        return xspcomm::sFmt(
            "CMD:%s, ADDR:%d, DATA:%d", this->str_cmd().c_str(),
            this->port["req_bits_addr"].U(), this->port["req_bits_wdata"].U());
    }
};

class SimpleBus2Message : public Device
{
    std::map<std::string, std::vector<MemBusMsg>> msg_queue;
    uint64_t *burst_index = 0;
    std::map<std::string, SimpleBusWrapper *> bus_map;
    std::map<std::string, uint64_t *> burst_index_map;
    xfunction<std::string, std::string, SimpleBusWrapper *> ch_map_fc = nullptr;

public:
    ~SimpleBus2Message()
    {
        for (auto &bus : this->bus_map) { delete bus.second; }
        delete this->burst_index;
    }
    SimpleBus2Message(
        std::string name, xspcomm::XPort &port,
        std::initializer_list<std::string> port_prefix,
        xfunction<std::string, std::string, SimpleBusWrapper *> ch_map_fc = nullptr,
        bool debug                                                    = false) :
        Device(name), ch_map_fc(ch_map_fc)
    {
        this->debug       = debug;
        this->burst_index = new uint64_t[port_prefix.size()];
        int i             = 0;
        for (auto &prefix : port_prefix) {
            this->bus_map[prefix]         = new SimpleBusWrapper(port, prefix);
            this->burst_index_map[prefix] = &this->burst_index[i];
            this->msg_queue[prefix]       = std::vector<MemBusMsg>();
            i++;
        }
    }

    void Step(uint64_t cycle = 0) override
    {
        if(cycle == this->cycle){
            return;
        }
        this->cycle = cycle;
        for (auto &bus : this->bus_map) {
            auto channel = bus.first;
            if (this->ch_map_fc != nullptr) {
                channel = this->ch_map_fc(channel, bus.second);
            }
            this->task(cycle, channel, *bus.second,
                       this->burst_index_map[bus.first],
                       this->msg_queue[bus.first]);
        }
    }

    void task(uint64_t c, std::string channel, SimpleBusWrapper &bus,
              uint64_t *burst_index, std::vector<MemBusMsg> &msg_queue)
    {
        bus.port["resp_valid"] = 0;
        bus.port["req_ready"]  = 1;
        // send
        if (bus.port["resp_ready"] == 1 && msg_queue.size() > 0) {
            MemBusMsg pmsg = msg_queue[0];
            msg_queue.erase(msg_queue.begin());
            bus.port["resp_valid"] = 1;
            bus.port["resp_bits_cmd"] =
                pmsg.IsReadRsp() ? bus.cmd_read : bus.cmd_writeResp;
            bus.port["resp_bits_rdata"] = pmsg.data;
            if (pmsg.IsBurstReadRspLast()) {
                bus.port["resp_bits_cmd"] = bus.cmd_readLast;
            }
            DebugC(this->debug, "%ld %s %s => RSP %s, queue_size: %ld", c,
                   this->name.c_str(), channel.c_str(), pmsg.String().c_str(),
                   msg_queue.size());
        }
        // recv
        if (bus.port["req_valid"] == 1) {
            MemBusMsg msg(64);
            // Read
            if (bus.IsReqRead()) {
                msg.ToRead();
                msg.addr = bus.port["req_bits_addr"];
                this->manager->SendMsg(channel, msg);
            } else if (bus.IsReqWrite()) {
                msg.ToWrite();
                msg.addr = bus.port["req_bits_addr"];
                msg.data = bus.port["req_bits_wdata"];
                msg.SetMaskFromBits(bus.port["req_bits_wmask"].U(), 8);
                this->manager->SendMsg(channel, msg);
            } else if (bus.IsReqReadBurst()) {
                msg.ToBurstRead(MemBusMsg::BurstType::Wra, 8);
                msg.addr = bus.port["req_bits_addr"];
                this->manager->SendMsg(channel, msg);
            } else if (bus.IsReqWriteBurst()) {
                msg.ToBurstWrite(MemBusMsg::BurstType::Wra, 8);
                msg.addr = bus.port["req_bits_addr"];
                msg.data = bus.port["req_bits_wdata"];
                msg.SetBurstIindex(*burst_index);
                msg.SetMaskFromBits(bus.port["req_bits_wmask"].U(), 8);
                this->manager->SendMsg(channel, msg);
                *burst_index += 1;
            } else if (bus.IsReqWriteLast()) {
                msg.ToBurstWrite(MemBusMsg::BurstType::Wra, 8);
                msg.addr = bus.port["req_bits_addr"];
                msg.data = bus.port["req_bits_wdata"];
                msg.SetBurstIindex(*burst_index);
                *burst_index += 1;
                Assert(msg.IsFullStage(),
                       "SimpleBus2Message: burst write last error");
                msg.SetMaskFromBits(bus.port["req_bits_wmask"].U(), 8);
                this->manager->SendMsg(channel, msg);
                *burst_index = 0;
            } else {
                Assert(false, "SimpleBus2Message: not support msg type");
            }
        }
    }
    bool IsMaster() override { return true; }
    bool Handle(std::string ch, Message &msg) override
    {
        if (this->msg_queue.find(ch) == this->msg_queue.end()) { return false; }
        MemBusMsg pmsg = *(MemBusMsg *)&msg;
        if (pmsg.IsReadRsp() || pmsg.IsWriteRsp()) {
            this->msg_queue[ch].push_back(pmsg);
            return true;
        } else {
            Assert(false, "SimpleBus2Message: not support msg type");
        }
        return false;
    }
};

} // namespace ovip

#endif
